/**
 * L2FProd.com Common Components 7.3 License.
 *
 * Copyright 2005-2007 L2FProd.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.l2fprod.common.swing.plaf.basic;

import com.l2fprod.common.swing.JOutlookBar;
import com.l2fprod.common.swing.PercentLayout;
import com.l2fprod.common.swing.PercentLayoutAnimator;
import com.l2fprod.common.swing.plaf.OutlookBarUI;
import com.l2fprod.common.util.JVM;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;

/**
 * BasicOutlookBarUI. <br>
 */
public class BasicOutlookBarUI extends BasicTabbedPaneUI implements OutlookBarUI {

  private static final String BUTTON_ORIGINAL_FOREGROUND = "TabButton/foreground";
  private static final String BUTTON_ORIGINAL_BACKGROUND = "TabButton/background";

  public static ComponentUI createUI(JComponent c) {
    return new BasicOutlookBarUI();
  }

  private ContainerListener tabListener;
  private Map buttonToTab;
  private Map tabToButton;
  private Component nextVisibleComponent;
  private PercentLayoutAnimator animator;

  public JScrollPane makeScrollPane(Component component) {
    // the component is not scrollable, wraps it in a ScrollableJPanel
    JScrollPane scroll = new JScrollPane();
    scroll.setBorder(BorderFactory.createEmptyBorder());
    if (component instanceof Scrollable) {
      scroll.getViewport().setView(component);
    } else {
      scroll.getViewport().setView(new ScrollableJPanel(component));
    }
    scroll.setOpaque(false);
    scroll.getViewport().setOpaque(false);
    return scroll;
  }

  protected void installDefaults() {
    super.installDefaults();

    TabLayout layout = new TabLayout();
    tabPane.setLayout(layout);
    // ensure constraints is correct for existing components
    layout.setLayoutConstraints(tabPane);
    updateTabLayoutOrientation();

    buttonToTab = new HashMap();
    tabToButton = new HashMap();

    LookAndFeel.installBorder(tabPane, "OutlookBar.border");
    LookAndFeel.installColors(tabPane, "OutlookBar.background", "OutlookBar.foreground");

    tabPane.setOpaque(true);

    // add buttons for the current components already added in this panel
    Component[] components = tabPane.getComponents();
    for (int i = 0, c = components.length; i < c; i++) {
      tabAdded(components[i]);
    }
  }

  protected void uninstallDefaults() {
    // remove all buttons created for components
    List tabs = new ArrayList(buttonToTab.values());
    for (Iterator iter = tabs.iterator(); iter.hasNext(); ) {
      Component tab = (Component) iter.next();
      tabRemoved(tab);
    }
    super.uninstallDefaults();
  }

  protected void installListeners() {
    tabPane.addContainerListener(tabListener = createTabListener());
    tabPane.addPropertyChangeListener(propertyChangeListener = createPropertyChangeListener());
    tabPane.addChangeListener(tabChangeListener = createChangeListener());
  }

  protected ContainerListener createTabListener() {
    return new ContainerTabHandler();
  }

  protected PropertyChangeListener createPropertyChangeListener() {
    return new PropertyChangeHandler();
  }

  protected ChangeListener createChangeListener() {
    return new ChangeHandler();
  }

  protected void uninstallListeners() {
    tabPane.removeChangeListener(tabChangeListener);
    tabPane.removePropertyChangeListener(propertyChangeListener);
    tabPane.removeContainerListener(tabListener);
  }

  public Rectangle getTabBounds(JTabbedPane pane, int index) {
    Component tab = pane.getComponentAt(index);
    return tab.getBounds();
  }

  public int getTabRunCount(JTabbedPane pane) {
    return 0;
  }

  public int tabForCoordinate(JTabbedPane pane, int x, int y) {
    int index = -1;
    for (int i = 0, c = pane.getTabCount(); i < c; i++) {
      if (pane.getComponentAt(i).contains(x, y)) {
        index = i;
        break;
      }
    }
    return index;
  }

  protected int indexOfComponent(Component component) {
    int index = -1;
    Component[] components = tabPane.getComponents();
    for (int i = 0; i < components.length; i++) {
      if (components[i] == component) {
        index = i;
        break;
      }
    }
    return index;
  }

  protected TabButton createTabButton() {
    TabButton button = new TabButton();
    button.setOpaque(true);
    return button;
  }

  protected void tabAdded(final Component newTab) {
    TabButton button = (TabButton) tabToButton.get(newTab);
    if (button == null) {
      button = createTabButton();

      // save the button original color,
      // later they would be restored if the button colors are not customized on
      // the OutlookBar
      button.putClientProperty(BUTTON_ORIGINAL_FOREGROUND, button.getForeground());
      button.putClientProperty(BUTTON_ORIGINAL_BACKGROUND, button.getBackground());

      buttonToTab.put(button, newTab);
      tabToButton.put(newTab, button);
      button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          final Component current = getVisibleComponent();
          Component target = newTab;

          // animate the tabPane if there is a current tab selected and if the
          // tabPane allows animation
          if (((JOutlookBar) tabPane).isAnimated() && current != target && current != null && target != null) {
            if (animator != null) {
              animator.stop();
            }
            animator = new PercentLayoutAnimator(tabPane, (PercentLayout) tabPane.getLayout()) {
              protected void complete() {
                super.complete();
                tabPane.setSelectedComponent(newTab);
                nextVisibleComponent = null;
                // ensure the no longer visible component has its constraint
                // reset to 100% in the case it becomes visible again without
                // animation
                if (current.getParent() == tabPane) {
                  ((PercentLayout) tabPane.getLayout()).setConstraint(current, "100%");
                }
              }
            };
            nextVisibleComponent = newTab;
            animator.setTargetPercent(current, 1.0f, 0.0f);
            animator.setTargetPercent(newTab, 0.0f, 1.0f);
            animator.start();
          } else {
            nextVisibleComponent = null;
            tabPane.setSelectedComponent(newTab);
          }
        }
      });
    } else {
      // the tab is already in the list, remove the button, it will be
      // added again later
      tabPane.remove(button);
    }

    // update the button with the tab information
    updateTabButtonAt(tabPane.indexOfComponent(newTab));

    int index = indexOfComponent(newTab);
    tabPane.add(button, index);

    // workaround for nullpointerexception in setRolloverTab
    // introduced by J2SE 5
    if (JVM.current().isOneDotFive()) {
      assureRectsCreated(tabPane.getTabCount());
    }
  }

  protected void tabRemoved(Component removedTab) {
    TabButton button = (TabButton) tabToButton.get(removedTab);
    tabPane.remove(button);
    buttonToTab.remove(button);
    tabToButton.remove(removedTab);
  }

  /**
   * Called whenever a property of a tab is changed
   *
   * @param index
   */
  protected void updateTabButtonAt(int index) {
    TabButton button = buttonForTab(index);
    button.setText(tabPane.getTitleAt(index));
    button.setIcon(tabPane.getIconAt(index));
    button.setDisabledIcon(tabPane.getDisabledIconAt(index));

    Color background = tabPane.getBackgroundAt(index);
    if (background == null) {
      background = (Color) button.getClientProperty(BUTTON_ORIGINAL_BACKGROUND);
    }
    button.setBackground(background);

    Color foreground = tabPane.getForegroundAt(index);
    if (foreground == null) {
      foreground = (Color) button.getClientProperty(BUTTON_ORIGINAL_FOREGROUND);
    }
    button.setForeground(foreground);

    button.setToolTipText(tabPane.getToolTipTextAt(index));
    button.setDisplayedMnemonicIndex(tabPane.getDisplayedMnemonicIndexAt(index));
    button.setMnemonic(tabPane.getMnemonicAt(index));
    button.setEnabled(tabPane.isEnabledAt(index));
    button.setHorizontalAlignment(((JOutlookBar) tabPane).getAlignmentAt(index));
  }

  protected TabButton buttonForTab(int index) {
    Component component = tabPane.getComponentAt(index);
    return (TabButton) tabToButton.get(component);
  }

  class ChangeHandler implements ChangeListener {
    public void stateChanged(ChangeEvent e) {
      JTabbedPane tabPane = (JTabbedPane) e.getSource();
      tabPane.revalidate();
      tabPane.repaint();
    }
  }

  class PropertyChangeHandler implements PropertyChangeListener {
    public void propertyChange(PropertyChangeEvent e) {
      //JTabbedPane pane = (JTabbedPane)e.getSource();
      String name = e.getPropertyName();
      if ("tabPropertyChangedAtIndex".equals(name)) {
        int index = ((Integer) e.getNewValue()).intValue();
        updateTabButtonAt(index);
      } else if ("tabPlacement".equals(name)) {
        updateTabLayoutOrientation();
      }
    }
  }

  protected void updateTabLayoutOrientation() {
    TabLayout layout = (TabLayout) tabPane.getLayout();
    int placement = tabPane.getTabPlacement();
    if (placement == JTabbedPane.TOP || placement == JTabbedPane.BOTTOM) {
      layout.setOrientation(PercentLayout.HORIZONTAL);
    } else {
      layout.setOrientation(PercentLayout.VERTICAL);
    }
  }

  /**
   * Manages tabs being added or removed
   */
  class ContainerTabHandler extends ContainerAdapter {

    public void componentAdded(ContainerEvent e) {
      if (!(e.getChild() instanceof UIResource)) {
        Component newTab = e.getChild();
        tabAdded(newTab);
      }
    }

    public void componentRemoved(ContainerEvent e) {
      if (!(e.getChild() instanceof UIResource)) {
        Component oldTab = e.getChild();
        tabRemoved(oldTab);
      }
    }
  }

  /**
   * Layout for the tabs, buttons get preferred size, tabs get all
   */
  protected class TabLayout extends PercentLayout {
    public void addLayoutComponent(Component component, Object constraints) {
      if (constraints == null) {
        if (component instanceof TabButton) {
          super.addLayoutComponent(component, "");
        } else {
          super.addLayoutComponent(component, "100%");
        }
      } else {
        super.addLayoutComponent(component, constraints);
      }
    }

    public void setLayoutConstraints(Container parent) {
      Component[] components = parent.getComponents();
      for (int i = 0, c = components.length; i < c; i++) {
        if (!(components[i] instanceof TabButton)) {
          super.addLayoutComponent(components[i], "100%");
        }
      }
    }

    public void layoutContainer(Container parent) {
      int selectedIndex = tabPane.getSelectedIndex();
      Component visibleComponent = getVisibleComponent();

      if (selectedIndex < 0) {
        if (visibleComponent != null) {
          // The last tab was removed, so remove the component
          setVisibleComponent(null);
        }
      } else {
        Component selectedComponent = tabPane.getComponentAt(selectedIndex);
        boolean shouldChangeFocus = false;

        // In order to allow programs to use a single component
        // as the display for multiple tabs, we will not change
        // the visible compnent if the currently selected tab
        // has a null component. This is a bit dicey, as we don't
        // explicitly state we support this in the spec, but since
        // programs are now depending on this, we're making it work.
        //
        if (selectedComponent != null) {
          if (selectedComponent != visibleComponent && visibleComponent != null) {
            // get the current focus owner
            Component currentFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
            // check if currentFocusOwner is a descendant of visibleComponent
            if (currentFocusOwner != null && SwingUtilities.isDescendingFrom(currentFocusOwner, visibleComponent)) {
              shouldChangeFocus = true;
            }
          }
          setVisibleComponent(selectedComponent);

          // make sure only the selected component is visible
          Component[] components = parent.getComponents();
          for (int i = 0; i < components.length; i++) {
            if (!(components[i] instanceof UIResource) && components[i].isVisible() && components[i] != selectedComponent) {
              components[i].setVisible(false);
              setConstraint(components[i], "*");
            }
          }

          if (BasicOutlookBarUI.this.nextVisibleComponent != null) {
            BasicOutlookBarUI.this.nextVisibleComponent.setVisible(true);
          }
        }

        super.layoutContainer(parent);

        if (shouldChangeFocus) {
          if (!requestFocusForVisibleComponent0()) {
            tabPane.requestFocus();
          }
        }
      }
    }
  }

  // PENDING(fred) JDK 1.5 may have this method from superclass
  @SuppressWarnings("deprecation")
  protected boolean requestFocusForVisibleComponent0() {
    Component visibleComponent = getVisibleComponent();
    if (visibleComponent.isFocusable()) {
      visibleComponent.requestFocus();
      return true;
    } else if (visibleComponent instanceof JComponent) {
      if (((JComponent) visibleComponent).requestDefaultFocus()) { return true; }
    }
    return false;
  }

  protected static class TabButton extends JButton implements UIResource {
    public TabButton() {}

    public TabButton(ButtonUI ui) {
      setUI(ui);
    }
  }

  //
  //
  //

  /**
   * Overriden to return an empty adapter, the default listener was
   * just implementing the tab selection mechanism
   */
  protected MouseListener createMouseListener() {
    return new MouseAdapter() {};
  }

  /**
   * Wraps any component in a Scrollable JPanel so it can work
   * correctly within a viewport
   */
  private static class ScrollableJPanel extends JPanel implements Scrollable {
    public ScrollableJPanel(Component component) {
      setLayout(new BorderLayout(0, 0));
      add("Center", component);
      setOpaque(false);
    }

    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
      return 16;
    }

    public Dimension getPreferredScrollableViewportSize() {
      return (super.getPreferredSize());
    }

    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
      return 16;
    }

    public boolean getScrollableTracksViewportWidth() {
      return true;
    }

    public boolean getScrollableTracksViewportHeight() {
      return false;
    }
  }

  //
  // Override all paint methods of the BasicTabbedPaneUI to do nothing
  //

  public void paint(Graphics g, JComponent c) {}

  protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {}

  protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {}

  protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {}

  protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {}

  protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {}

  protected void paintFocusIndicator(Graphics g,
      int tabPlacement,
      Rectangle[] rects,
      int tabIndex,
      Rectangle iconRect,
      Rectangle textRect,
      boolean isSelected) {}

  protected void paintIcon(Graphics g, int tabPlacement, int tabIndex, Icon icon, Rectangle iconRect, boolean isSelected) {}

  protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect) {}

  protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {}

  protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {}

  protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {}

  protected void paintText(Graphics g,
      int tabPlacement,
      Font font,
      FontMetrics metrics,
      int tabIndex,
      String title,
      Rectangle textRect,
      boolean isSelected) {}
}